package com.audaque.encoding.coding;

import com.alibaba.excel.EasyExcel;
import com.audaque.encoding.dto.*;
import com.audaque.encoding.entity.CallbackDO;
import com.audaque.encoding.entity.HandleCallbackDO;
import com.audaque.encoding.entity.RequestNewDO;
import com.audaque.encoding.entity.SourceConfigNewDO;
import com.audaque.encoding.enums.DbTypeEnum;
import com.audaque.encoding.listener.PostgreSQLListener;
import com.audaque.encoding.mapper.PostgreSQLCommonMapper;
import com.audaque.encoding.service.CallbackService;
import com.audaque.encoding.service.HandleCallbackService;
import com.audaque.encoding.service.RequestNewService;
import com.audaque.encoding.service.SourceConfigNewService;
import com.audaque.encoding.util.FileUtil;
import com.audaque.encoding.util.JsonUtil;
import com.audaque.encoding.util.TimeUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import jakarta.annotation.Resource;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;
import org.springframework.util.StreamUtils;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.*;

/**
 * @author mayong
 * @since 2024/7/1
 */
@Slf4j
@Service
public class CodingV3Service {

    @Resource
    private SourceConfigNewService sourceConfigNewService;

    @Resource
    private PostgreSQLCommonMapper postgreSQLCommonMapper;

    @Resource
    private CoreService coreService;

    @Resource
    private RequestNewService requestNewService;

    @Resource
    private CallbackService callbackService;

    @Resource
    private HandleCallbackService handleCallbackService;

    @Resource
    private ApplicationContext applicationContext;

    /**
     * 必备六要素
     */
    private static final List<String> REQUIRED_ELEMENTS = Arrays.asList("ysmc", "dl", "zl", "xl", "xzqh", "gldw");

    /**
     * 需要过滤的元素 （北斗网格位置码、赋码结果字段） List.of("bdwgwzm")
     */
    private static final List<String> FILTER_ELEMENTS = new ArrayList<>();

    /**
     * 必备六要素值
     */
    private static final List<String> REQUIRED_ELEMENTS_VAL = Arrays.asList("要素名称", "大类", "中类", "小类", "行政区划", "管理单位");


    // 赋码请求文件存放路径
    private static final String REQUEST_EXCEL_DIR_PREFIX = System.getProperty("user.dir") + File.separator + "request" + File.separator + LocalDate.now() + File.separator;
    // 赋码结果文件存放路径
    private static final String RESPONSE_EXCEL_DIR_PREFIX = System.getProperty("user.dir") + File.separator + "download" + File.separator + LocalDate.now();


    public void startNow() {
        log.info("手动执行任务.............");
        List<SourceConfigNewDO> list = sourceConfigNewService.list(Wrappers.<SourceConfigNewDO>lambdaQuery().eq(SourceConfigNewDO::getScan, Boolean.TRUE));
        startCore(list);
        log.info("手动执行任务结束.");
    }


    public void startTask(Integer type) {
        log.info("开始执行定时任务............." + type);
        // 只要配置了定时任务，就执行该任务，不用管scan字段
        List<SourceConfigNewDO> list = sourceConfigNewService.list(Wrappers.<SourceConfigNewDO>lambdaQuery()
                .eq(SourceConfigNewDO::getCronType, type)
//                .eq(SourceConfigNewDO::getScan, Boolean.TRUE)
        );

        startCore(list);
        log.info("定时任务执行完毕.");
    }


    /**
     * 开始处理自动赋码逻辑
     *
     * @param list 配置表数据
     */
    private void startCore(List<SourceConfigNewDO> list) {
        if (list.isEmpty()) {
            log.info("获取赋码配置表为空");
            return;
        }
        int i = 1;
        // 扫描配置表，开始处理自动赋码逻辑
        for (SourceConfigNewDO sourceConfigNewDO : list) {
            log.info("开始处理第 " + i + " 张表,表名: " + sourceConfigNewDO.getSourceTableName());
            DbTypeEnum type = sourceConfigNewDO.getType();
            if (type == DbTypeEnum.PG) {

                log.info("执行生成excel文件的逻辑");
                String remark = null;
                try {
                    //检查赋码结果字段是否存在
                    checkField(sourceConfigNewDO.getSourceTableName(), sourceConfigNewDO.getSchema(), sourceConfigNewDO.getResult());
                    //获取数据,生成赋码文件
                    ExcelInfo excelInfo = generateExcel(sourceConfigNewDO.getFileNamePrefix(),
                            sourceConfigNewDO.getSchema(),
                            sourceConfigNewDO.getSourceTableName(), sourceConfigNewDO.getResult(), sourceConfigNewDO.getTimeFields());
                    //开始赋码
                    sendCodingRequest(excelInfo.getPath(), String.valueOf(sourceConfigNewDO.getId()), excelInfo.getSum());
                } catch (Exception e) {
                    remark = e.getMessage();
                    e.printStackTrace();
                }
                sourceConfigNewService.update(Wrappers.<SourceConfigNewDO>lambdaUpdate()
                        .eq(SourceConfigNewDO::getId, sourceConfigNewDO.getId())
                        .set(SourceConfigNewDO::getScan, Boolean.FALSE)
                        .set(SourceConfigNewDO::getRemark, remark));
            } else {
                log.error("暂不支持处理该类型的数据库");
                continue;
            }
            log.info("第 " + i++ + " 张表处理完毕!");
        }
    }

    /**
     * 执行赋码请求逻辑
     *
     * @param filepath      赋码文件路径地址
     * @param tableConfigId 配置表id
     * @param sum           请求数据行
     * @throws IOException 文件流异常
     */
    public void sendCodingRequest(String filepath, String tableConfigId, int sum) throws IOException {
        log.info("执行发送请求的逻辑");
        String filename = filepath.substring(filepath.lastIndexOf(File.separator) + 1);
        GetFilepathRes res = coreService.getFilePath(GetFilepathReq.builder()
                .fileName(filename)
                .chunkCount(1)
                .build());
        requestNewService.save(RequestNewDO.builder()
                .tableConfigId(tableConfigId)
                .reqFileId(res.getId())
                .reqFilename(filename)
                .sum(sum)
                .reqTime(LocalDateTime.now())
                .build());
        File file = new File(filepath);
        InputStream inputStream = new FileInputStream(file);
        // 使用Spring的StreamUtils将文件内容转换为字节数组
        byte[] byteArray = StreamUtils.copyToByteArray(inputStream);
        String[] uploadUrls = res.getUploadUrls();
        coreService.chunkUpload(uploadUrls[0], byteArray);
        coreService.merge(res.getId());
        coreService.codeTask(res.getId());
    }

    private void checkField(String tableName, String schema, String key) {
        // 检查赋码的6个必要字段是否存在
        // 检查赋码结果字段是否存在
        String s = postgreSQLCommonMapper.hasKeyField(tableName, schema, key);
        if (StringUtils.equals(s, "f")) {
            //创建
            postgreSQLCommonMapper.createField(tableName, schema, key);
        }
    }


    /**
     * 根据表名生成赋码请求的excel文件
     *
     * @param prefix     赋码文件前缀
     * @param schema     模式
     * @param tableName  表名
     * @param key        赋码字段
     * @param timeFields 需要格式化处理的时间字段
     * @return excel文件信息
     */
    private ExcelInfo generateExcel(String prefix, String schema, String tableName, String key, String timeFields) {
        List<TableFieldInfo> fields = postgreSQLCommonMapper.getTableFieldInfo(schema, tableName);
        //表头排序处理
        HeaderFields headerFields = headers(fields, key);
        //生成文件路径
        FileUtil.createPathIfNotExists(REQUEST_EXCEL_DIR_PREFIX);
        String fileName = REQUEST_EXCEL_DIR_PREFIX + prefix + "_" + TimeUtil.stringDateTime() + ".xlsx";
        tableName = schema + "." + tableName;
        //获取行数据
        List<List<Object>> data = getData(headerFields.getHeaderFields(), tableName, key, timeFields);
        if(data.isEmpty()){
            throw new RuntimeException("需要赋码的数据为空");
        }
        // 写出excel
        List<List<String>> headers = headerFields.getHeaders();
        EasyExcel.write(fileName).head(headers).sheet("赋码数据").doWrite(data);
        int size = data.size();
        return ExcelInfo.builder()
                .path(fileName)
                .sum(size)
                .build();

    }

    // 根据表头获取数据
    private List<List<Object>> getData(LinkedList<String> fields, String tableName, String key, String timeFields) {
        // key 赋码结果标识字段
        List<LinkedHashMap<String, String>> data = postgreSQLCommonMapper.getData(fields, tableName, key);
        LinkedList<List<Object>> res = new LinkedList<>();
        for (LinkedHashMap<String, String> map : data) {
            LinkedList<String> list = new LinkedList<>();
            for (Map.Entry<String, String> entry : map.entrySet()) {
                String mapKey = entry.getKey();
                Object value = entry.getValue();
                if (value == null) {
                    list.add(null);
                } else {
                    // 截取超长字符
                    if(value.toString().length() > 500){
                        value = value.toString().substring(0, 200);
                    }
                    // 如果 key 是指定的 keyToProcess，进行特殊处理
                    if (StringUtils.isNotEmpty(timeFields) && Arrays.asList(timeFields.split(",")).contains(mapKey)) {
                        list.add(value.toString().substring(0, 10).replace("-", ""));
                    } else {
                        list.add(value.toString());
                    }
                }
            }
            res.addLast(new LinkedList<>(list));
        }
        return res;
    }

    /*
    将每张表的必备要素放在最前面
     */
    private HeaderFields headers(List<TableFieldInfo> fields, String key) {

        LinkedList<List<String>> headers = new LinkedList<>();
        LinkedList<String> headerFields = new LinkedList<>();
        FILTER_ELEMENTS.add(key);
        //  过滤字段（统一识别代码 tysbdm  ,北斗网格位置码 bdwgwzm）
        for (TableFieldInfo fieldInfo : fields) {
            if (!FILTER_ELEMENTS.contains(fieldInfo.getFieldName())) {
                headers.addLast(Collections.singletonList(fieldInfo.getComment()));
                headerFields.addLast(fieldInfo.getFieldName());
            }
        }
        return HeaderFields.builder()
                .headers(headers)
                .headerFields(headerFields)
                .build();
    }

    public void handleCallback(CallbackDTO dto) {
        CallbackDO one = callbackService.getOne(Wrappers.<CallbackDO>lambdaQuery().eq(CallbackDO::getNotifyId, dto.getId()));
        if (one == null) {
            String reqFileId = dto.getContent() == null ? null : dto.getContent().getSrcFileId();
            String resFileId = dto.getContent() == null ? null : dto.getContent().getFileId();
            callbackService.save(CallbackDO.builder()
                    .id(null)
                    .notifyId(dto.getId())
                    .state(dto.getType())
                    .reqFileId(reqFileId)
                    .resFileId(resFileId)
                    .createTime(LocalDateTime.now())
                    .json(JsonUtil.json(dto))
                    .build());
            if(dto.getContent() != null && StringUtils.isNotEmpty(dto.getContent().getErrMsg())){
                log.error("回调失败，原因：{}", dto.getContent().getErrMsg());
                callbackService.updateStatus(dto.getContent().getSrcFileId(), dto.getContent().getErrMsg());
                return;
            }
            handle(dto);
        } else {
            log.error("该回调消息已经被处理。");
        }
    }

    /**
     * 回调数据处理逻辑
     *
     * @param dto 回调参数
     */
    private void handle(CallbackDTO dto) {
        String reqFileId = dto.getContent() == null ? null : dto.getContent().getSrcFileId();
        String resFileId = dto.getContent() == null ? null : dto.getContent().getFileId();
        if (StringUtils.isNotEmpty(reqFileId)) {
            //  记录日志和执行文件下载的逻辑
            FileInfoRes info = coreService.info(resFileId);
            // 赋码后的文件地址，根据日期划分
            FileUtil.createPathIfNotExists(RESPONSE_EXCEL_DIR_PREFIX);
            String path = FileUtil.checkExistAndRandom(RESPONSE_EXCEL_DIR_PREFIX, info.getFileName());
            coreService.downLoad(info.getChunks().get(0).getUrl(), path);
            // 获取上传的文件信息
            log.info("待执行excel文件解析，更新落库的逻辑。");
            prepareDataParsingExcel(path, info.getFileName(), reqFileId, dto.getId());
        } else {
            log.error("源文件id为空");
            throw new RuntimeException("源文件id为空");
        }
    }

    public void prepareDataParsingExcel(String filepath, String fileName, String reqFileId, String notifyId) {

        // 根据文件名的前6位解析出所属分类
        String typeCode = fileName.substring(0, 6);
        // 用typeCode去模糊匹配前缀字段
        SourceConfigNewDO configNewDO = sourceConfigNewService.getOne(Wrappers.<SourceConfigNewDO>lambdaQuery()
                .likeRight(SourceConfigNewDO::getFileNamePrefix, typeCode));
        if (configNewDO == null) {
            log.error("{}回调文件未找到对应的分类配置", filepath);
            return;
        }
        //根据配置的模式和表名获取表头
        List<TableFieldInfo> fields = postgreSQLCommonMapper.getTableFieldInfo(configNewDO.getSchema(), configNewDO.getSourceTableName());

        RequestNewDO requestDO = requestNewService.getOne(Wrappers.<RequestNewDO>lambdaQuery().eq(RequestNewDO::getReqFileId, reqFileId));

        HandleCallbackDO build = HandleCallbackDO.builder()
                .notifyId(notifyId)
                .requestId(String.valueOf(requestDO == null ? null : requestDO.getId()))
                .filepath(filepath)
                .build();
        handleCallbackService.save(build);
        log.info("开始解析excel文件");
        AnalysisInfo analysisInfo = AnalysisInfo.builder()
                .fields(fields)
                .callBackId(String.valueOf(build.getId()))
                .targetSchema(configNewDO.getTargetSchema())
                .targetTableName(configNewDO.getTargetTableName())
                .database(configNewDO.getTargetDatabase())
                .schema(configNewDO.getSchema())
                .tableName(configNewDO.getSourceTableName())
                .resultName(configNewDO.getResultName())
                .resultField(configNewDO.getResult())
                .bizName(configNewDO.getBizName())
                .bizField(configNewDO.getBizId())
                .build();
        // 创建postgreSQLListener对象，设置必要参数
        PostgreSQLListener postgreSQLListener = applicationContext.getBean(PostgreSQLListener.class);
        postgreSQLListener.setPostgreSQLCommonMapper(postgreSQLCommonMapper);
        postgreSQLListener.setHandleCallbackService(handleCallbackService);
        postgreSQLListener.setAnalysisInfo(analysisInfo);

        EasyExcel.read(filepath, postgreSQLListener).sheet().doRead();
    }


    public void reAnalyze(String id) {
        HandleCallbackDO handleCallbackDO = handleCallbackService.getById(id);
        if (handleCallbackDO == null) {
            throw new RuntimeException("查询数据失败");
        }
        RequestNewDO requestNewDO = requestNewService.getById(handleCallbackDO.getRequestId());
        // 重新解析文件
        prepareDataParsingExcel(handleCallbackDO.getFilepath(), requestNewDO.getReqFilename(), requestNewDO.getReqFileId(), handleCallbackDO.getNotifyId());
    }


    @Data
    @Builder
    @NoArgsConstructor
    @AllArgsConstructor
    public static class HeaderFields {
        private List<List<String>> headers;
        private LinkedList<String> headerFields;
    }

    @Data
    @Builder
    @NoArgsConstructor
    @AllArgsConstructor
    public static class ExcelInfo {
        private int sum;
        private String path;
    }

}
